/*
 *
 *
Copyright (c) 1998-2001 Aristar, Inc. All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

3. The end-user documentation included with the redistribution, if any,
must include the following acknowledgment:

"This product includes software developed by Aristar, Inc. (http://www.aristar.com/)."

Alternately, this acknowledgment may appear in the software itself, if and
wherever such third-party acknowledgments normally appear.

4. The names "Dialect" and "Aristar, Inc." must not be used to endorse or
promote products derived from this software without prior written permission.
For written permission, please contact info@aristar.com.

5. Products derived from this software may not be called "Dialect", nor may
"Dialect" appear in their name, without prior written permission of Aristar, Inc.

THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
ARISTAR, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

This software consists of voluntary contributions made by many individuals
on behalf of Aristar, Inc.  For more information on Aristar, Inc.,
please see <http://www.aristar.com/>.

Portions of this software are based upon software freely available and
written by individuals not associated with Aristar, Inc.  Copyrights are
maintained in the relevant source code files.
 *
 *
 */

#include "stdafx.h"
#include "CRegExpression.h"

///////////////////////////////////
#if defined(USING_MFC) && defined(_DEBUG)
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
///////////////////////////////////

CRegExpression::CRegExpression()
{
	m_ReplaceCount = 0;
	m_Pattern = NULL;
	m_Matches = NULL;
	m_MatchData = NULL;
	m_DataToSearch = NULL;
	memset(&m_RegEx, 0, sizeof(regex_t));
	memset(m_LastErrorBuf, 0, 256 * sizeof(TCHAR));
	memset(m_cLastErrorBuf, 0, 256);
}

CRegExpression::~CRegExpression()
{
	FreeMatchData();
	regfree(&m_RegEx);
	delete[] m_Matches;
	delete[] m_Pattern; m_Pattern = NULL;
	delete[] m_DataToSearch; m_DataToSearch = NULL;
}

void CRegExpression::Reset()
{
	m_ReplaceCount = 0;
	FreeMatchData();
	regfree(&m_RegEx); memset(&m_RegEx, 0, sizeof(regex_t));
	delete[] m_Matches;	m_Matches = NULL;
	delete[] m_Pattern; m_Pattern = NULL;
	delete[] m_DataToSearch; m_DataToSearch = NULL;
	memset(m_LastErrorBuf, 0, 256 * sizeof(TCHAR));
	memset(m_cLastErrorBuf, 0, 256);
}

void CRegExpression::TCopy(char* dest, const TCHAR* src, int length)
{
	// Length includes space for NULL terminator

	if ((src == NULL) || (*src == 0)) {
		dest[0] = 0;
	}

	for(int idx = 0; idx < length; ++idx) {
		dest[idx] = (char)(src[idx]);
	}
}

bool CRegExpression::Compile(const TCHAR* pzPattern, int modifiers)
{
	if (m_Pattern != NULL) Reset();
	int length = _tcslen(pzPattern) + 1;
	m_Pattern = new char[length];
	TCopy(m_Pattern, pzPattern, length);

	m_LastError = regcomp(&m_RegEx, m_Pattern, REG_EXTENDED | modifiers);

	if (m_LastError) {
		regerror(m_LastError, &m_RegEx, m_cLastErrorBuf, 256);	// Get error string
		delete[] m_Pattern; m_Pattern = NULL;	// Wipe text pattern buffer
		regfree(&m_RegEx);	// This probably isn't really be needed
		return false;
	}
	return true;
}

int CRegExpression::GetReplaceCount() const
{
	return m_ReplaceCount;
}

int CRegExpression::NumberSubs()
{
	if (m_Pattern == NULL) {
		m_LastError = 0;	// Not really...
		return 0;
	}
	return m_RegEx.re_nsub;
}

int CRegExpression::Match(const TCHAR* pz)
{
	if (m_Pattern == NULL) {
		_stprintf(m_LastErrorBuf, _T("Called Match() before pattern was compiled"));
		return -2;
	}

	if (m_DataToSearch != NULL) delete[] m_DataToSearch;
	int length = _tcslen(pz) + 1;
	m_DataToSearch = new char[length];
	TCopy(m_DataToSearch, pz, length);

	if (m_Matches != NULL) {
		delete[] m_Matches;
	}
	int n = m_RegEx.re_nsub + 1;
	m_Matches = new regmatch_t[n];
	m_LastError = regexec(&m_RegEx, m_DataToSearch, n, m_Matches, 0);
	if (m_LastError == REG_NOMATCH) {
		if (m_MatchData != NULL) FreeMatchData();
		return -1;
	}
	else if (m_LastError) {
		regerror(m_LastError, &m_RegEx, m_cLastErrorBuf, 256);
		return -2;
	}

	// Store data in a way outside folks may find more useful...
	if (m_MatchData != NULL) FreeMatchData();
	m_MatchData = new matchdata_t[n];
	for (int idx = 0; idx < n; ++idx) {
		int startOff = m_Matches[idx].rm_so;
		int endOff = m_Matches[idx].rm_eo;

		m_MatchData[idx].arraySize = n;
		m_MatchData[idx].startPos = startOff;

		if (startOff < 0) {
			m_MatchData[idx].length = 0;
			m_MatchData[idx].pzText = NULL;
		}
		else {
			int stripLength = endOff - startOff;
			m_MatchData[idx].length = stripLength;
			m_MatchData[idx].pzText = new TCHAR[stripLength + 1];
			for (int loop = 0; loop < stripLength; ++loop) {
				m_MatchData[idx].pzText[loop] = (TCHAR)(m_DataToSearch[startOff + loop]);
			}
			m_MatchData[idx].pzText[stripLength] = 0;
		}
	}
	return m_Matches[0].rm_so;	// Return first char position
}

void CRegExpression::FreeMatchData()
{
	if (m_MatchData == NULL) return;

	int length = m_MatchData[0].arraySize;
	for (int idx = 0; idx < length; ++idx) {
		delete[] m_MatchData[idx].pzText;
	}
	delete[] m_MatchData;
	m_MatchData = NULL;
}

const matchdata_t* CRegExpression::GetMatchData() const
{
	return m_MatchData;
}

int CRegExpression::GetLastErrorCode() const
{
	return m_LastError;
}

const TCHAR* CRegExpression::GetLastError()
{
	int idx = -1;
	while (m_cLastErrorBuf[++idx] != 0) {
		m_LastErrorBuf[idx] = (TCHAR)(m_cLastErrorBuf[idx]);
	}
	m_LastErrorBuf[idx] = 0;
	return m_LastErrorBuf;
}

int CRegExpression::Replace(LPCTSTR pzSource, LPCTSTR pzRText, int replaceCount, TString& result)
{
	m_ReplaceCount = 0;
	result.Empty();

	if ((pzSource == NULL) || (*pzSource == 0)) return 0;

	CReplacement r;
	int err = r.Compile(pzRText, NumberSubs());
	if (err < 0) {
		switch (err) {
			case -1:
				strcpy(m_cLastErrorBuf, "Replacement text uses group number larger than exists within the regex");
				break;
			case -2:
				strcpy(m_cLastErrorBuf, "Invalid escape sequence in replacement text");
				break;
			default:
				strcpy(m_cLastErrorBuf, "Unkown error when parsing replacement text for escapes");
		}
		return -1;
	}

	int index;
	bool bReplaceAll = replaceCount <= 0;
	while (*pzSource != 0) {
		if (!bReplaceAll && (replaceCount-- <= 0)) {
			// Reached the number of replacements the user requested
			// Notice that an initial replaceCount of zero or less means
			// replace all occurances...
			result += pzSource;
			break;
		}

		index = Match(pzSource);
		if (index < 0) {
			result += pzSource;	// Tack on the remainder
			break;
		}
		else if ((m_Pattern != NULL) && (m_Pattern[0] == '^')) {
#pragma message("****** Check regex.replace() with bol character ******")
			// This can only match once (unless we say account for carriage returns)
			result += r.GetReplacement(m_MatchData);
			break;
		}
		else {
			++m_ReplaceCount;

			TCHAR* temp = new TCHAR[index + 1];
			_tcsncpy(temp, pzSource, index);
			temp[index] = 0;
			result += temp;
			delete[] temp;

			result += r.GetReplacement(m_MatchData);

			pzSource += m_Matches[0].rm_eo;
		}
	}

	// Not really applicable to outside world except after Match()
	FreeMatchData();
	delete[] m_Matches;
	m_Matches = NULL;

	return m_ReplaceCount;
}

/////////////////////////////////////////////////////////////
// The replacement class declared in CRegExpression and
// used as a helper in CRegExpression::Replace()
/////////////////////////////////////////////////////////////

CRegExpression::CReplacement::CReplacement()
{
	iPieces = 0;
	for (int idx = 0; idx < MAX_REPL_PARTS; ++idx) {
		pParts[idx] = NULL;
	}
}

CRegExpression::CReplacement::~CReplacement()
{
	for (int idx = 0; idx < iPieces; ++idx) {
		if (pParts[idx] > (TString*)9) {
			delete pParts[idx];
		}
		pParts[idx] = NULL;
	}
	iPieces = 0;
}

void CRegExpression::CReplacement::FreeParts()
{
	for (int idx = 0; idx < iPieces; ++idx) {
		if (pParts[idx] > (TString*)9) {
			delete pParts[idx];
		}
		pParts[idx] = NULL;
	}
	iPieces = 0;
}

TString CRegExpression::CReplacement::GetReplacement(matchdata_t* m)
{
	TString result;
	for (int idx = 0; idx < iPieces; ++idx) {
		if (pParts[idx] > (TString*)9) {
			result += *pParts[idx];
		}
		else {
			// We replace with the given substring
			ASSERT(m != NULL);
			result += m[(int)(pParts[idx])].pzText;
		}
	}
	return result;
}

int CRegExpression::CReplacement::Compile(LPCTSTR pzText, int numSubs)
{
	FreeParts();	// Will set iPieces -> 0

	if (pzText == NULL) {
		return true;
	}

	TString* part = new TString();
	while (true) {
		if (*pzText == '\\') {
			switch (*(++pzText)) {
				case '\\':	*part += '\\';	break;
				case 'n':	*part += '\n';	break;
				case 'r':	*part += '\r';	break;
				case 't':	*part += '\t';	break;
				case 'f':	*part += '\f';	break;

				case '0': case '1': case '2': case '3': case '4':
				case '5': case '6': case '7': case '8': case '9':
					{
						int group = (*pzText) - '0';
						if (group > numSubs) {
							delete part;
							FreeParts();
							return -1;
						}
						pParts[iPieces++] = part;				// Save text up to this point
						pParts[iPieces++] = (TString*)group;	// Store group number
						part = new TString();					// Begin new text piece
					}
					break;
				default:
					delete part;
					FreeParts();
					return -2;
			}
		}
		else if (*pzText == 0) {
			// End of replacement text
			pParts[iPieces++] = part;
			break;
		}
		else {
			// Just another character
			*part += *pzText;
		}
		++pzText;
	}
	return true;
}

/////////////////////////////////////////////////////////////
// Quick hack, this doesn't really belong here...
#ifdef __WINCE__
void sprintf(char* buf, char* format, int item) {
	// Special case used engine.c and regerror.c
	TCHAR* wFormat;
	TCHAR* wBuf;

	int length = strlen(format);
	wFormat = new TCHAR[length + 1];
	wBuf = new TCHAR[length * 2];	// Hack, yes I know...

	for (int idx = 0; idx < length; idx++) {
		wFormat[idx] = (TCHAR)(format[idx]);
	}
	wFormat[length] = 0;

	wsprintf(wBuf, wFormat, item);

	idx = 0;
	while (wBuf[idx] != 0) {
		buf[idx] = (char)(wBuf[idx]);
		++idx;
	}
	buf[idx] = 0;

	delete[] wFormat;
	delete[] wBuf;
}
#endif
